﻿using Dpy.MathWinRT.Geometry.Geometry2D;
using Dpy.MathWinRT;
using System;
using System.Collections.Generic;
using System.Linq;

namespace BouncingBallGame.ViewModels.ShapeViewModels
{
    public class BallViewModel : CircleViewModel
    {
        #region Fields

        private Vector2D speed;
        private const double MinimalPixelSpeed = 5;

        #endregion


        #region Properties

        public double NormalSpeedLoss { get; set; }
        public double TangentSpeedLoss { get; set; }

        public Vector2D Speed
        {
            get
            {
                return this.speed;
            }
            set
            {
                if (value.X.IsRealNumber() && value.Y.IsRealNumber())
                {
                    this.speed = value;
                    if (this.speed.Length < BallViewModel.MinimalPixelSpeed)
                    {
                        this.speed = new Vector2D();
                    }
                }
            }
        }

        public bool IsMoving
        {
            get
            {
                return !this.Speed.IsZero();
            }
        }

        #endregion


        #region Constructor

        public BallViewModel(double x, double y, double radius)
            :this(x, y, radius, 0.6, 0.8)
        {
        }

        public BallViewModel(double x, double y, double radius, double normalSpeedLoss, double tangentSpeedLoss)
            : base(x, y, radius)
        {
            this.Speed = new Vector2D();
            this.NormalSpeedLoss = normalSpeedLoss;
            this.TangentSpeedLoss = tangentSpeedLoss;
        }

        #endregion


        #region Methods

        public void Move(double seconds)
        {
            Vector2D translation = this.Speed * seconds;
            this.X += translation.X;
            this.Y += translation.Y;
        }

        public void Move(double distance, out double secondsUsed)
        {
            double distancePerSecond = this.Speed.Length;
            secondsUsed = distance / distancePerSecond;

            this.Move(secondsUsed);
        }  

        public void ChangeSpeedFromBounce(Vector2D lineNormal)
        {
            Vector2D i = lineNormal.Clone();

            if (i.TryNormalize())
            {
                Vector2D j = new Vector2D(-(i.Y), i.X);
                Vector2D newSpeed = (-this.NormalSpeedLoss * (this.Speed * i)) * i + (this.TangentSpeedLoss * this.Speed * j) * j;
                this.Speed = newSpeed;
            }
            else
            {
                this.Speed *= this.NormalSpeedLoss;
            }
        }

        public bool TryCollide(IEnumerable<LineSegment2D> lineSegments, double secondsToMove, out double nearestDistance, out Vector2D collisionNormal)
        {
            if (this.TryCollide(lineSegments, out nearestDistance, out collisionNormal))
            {
                if (nearestDistance.IsZero())
                {
                    this.ChangeSpeedFromBounce(collisionNormal);

                    return true;
                }
                else if (nearestDistance <= this.Speed.Length * secondsToMove)
                {
                    return true;
                }
            }

            return false;
        }

        private bool TryCollide(IEnumerable<LineSegment2D> lineSegments, out double nearestDistance, out Vector2D collisionNormal)
        {
            nearestDistance = double.MaxValue;
            collisionNormal = new Vector2D();
            bool isColliding = false;

            foreach (LineSegment2D segment in lineSegments)
            {
                double distance;
                Vector2D normal;
                if (this.TryCollide(segment, out distance, out normal))
                {
                    isColliding = true;

                    if (distance.IsZero())
                    {
                        if (!nearestDistance.IsZero())
                        {
                            nearestDistance = 0;
                            collisionNormal = new Vector2D();
                        }

                        collisionNormal += normal;
                    }
                    else if (distance <= nearestDistance)
                    {
                        nearestDistance = distance;
                        collisionNormal = normal;
                    }
                }
            }

            return isColliding;
        }

        private bool TryCollide(LineSegment2D segment, out double distance, out Vector2D collisionNormal)
        {       
            var circle = this.Circle;
            distance = 0;
            collisionNormal = null;

            Vector2D[] intersectionsWithSegment = segment.IntersectWith(circle);
            if(intersectionsWithSegment != null && intersectionsWithSegment.Length > 0)
            {
                if (this.IsCollidingWithoutMoving(intersectionsWithSegment, out distance, out collisionNormal))
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }            

            double lineCoordinate;
            Vector2D centerProjection = segment.GetPointProjection(circle.Center, out lineCoordinate);

            Vector2D radiusVector = centerProjection - circle.Center;
            if (radiusVector.Length.EssentiallyEquals(circle.Radius) && lineCoordinate >= 0 && lineCoordinate <= 1)
            {
                if (this.IsCollidingWithoutMoving(new Vector2D[] { centerProjection }, out distance, out collisionNormal))
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }

            if (!this.Speed.IsZero() && !radiusVector.IsZero())
            {
                Vector2D tangentPoint = circle.GetPointFromRadiusVector(radiusVector);
                Ray2D tangetIntesector = new Ray2D(tangentPoint, tangentPoint + this.Speed);

                double segmentCoordinate;
                Vector2D collisionPoint = segment.IntersectWith(tangetIntesector, out segmentCoordinate, out distance);

                if (collisionPoint == null)
                {
                    Vector2D nearestEnd = segmentCoordinate > 1 ? segment[1] : segment[0];
                    Ray2D endIntersector = new Ray2D(nearestEnd, -this.Speed);

                    double[] rayCoordinates;
                    Vector2D[] intersections = endIntersector.IntersectWith(circle, out rayCoordinates);
                    if (intersections != null && intersections.Length > 0)
                    {
                        if (intersections.Length == 2)
                        {
                            collisionPoint = rayCoordinates[0] < rayCoordinates[1] ? intersections[0] : intersections[1];
                        }
                        else
                        {
                            collisionPoint = intersections[0];
                        }

                        distance = (collisionPoint - nearestEnd).Length;
                        collisionNormal = (circle.Center - collisionPoint);

                        return true;
                    }
                }
                else
                {
                    distance = (tangentPoint - collisionPoint).Length;
                    collisionNormal = (circle.Center - tangentPoint);

                    return true;
                }
            }

            return false;
        }

        private bool IsCollidingWithoutMoving(Vector2D[] intersectionsWithSegments, out double distance, out Vector2D collisionNormal)
        {
            distance = 0;
            collisionNormal = null;

            if (intersectionsWithSegments != null && intersectionsWithSegments.Length > 0)
            {
                Vector2D intersection = intersectionsWithSegments[0];

                if (intersectionsWithSegments.Length == 2)
                {
                    intersection = (intersectionsWithSegments[0] + intersectionsWithSegments[1]) * 0.5;
                }

                Vector2D normalVector = this.Center - intersection;
                if (normalVector.TryNormalize())
                {
                    double scalarProjection = this.Speed * normalVector;

                    if (scalarProjection > 0)
                    {
                        return false;
                    }
                    else if (scalarProjection < 0)
                    {
                        distance = 0;
                        collisionNormal = normalVector;

                        return true;
                    }
                }
            }

            return false;
        }

        public bool IsStable(IEnumerable<LineSegment2D> segments)
        {
            double nearestIntersection;
            Vector2D collisionNormal;

            if(this.TryCollide(segments, out nearestIntersection, out collisionNormal))
            {
                collisionNormal.TryNormalize();
                
                if(collisionNormal.EssentiallyEquals(new Vector2D(0, -1)) && Math.Abs(this.Speed.X) < BallViewModel.MinimalPixelSpeed)
                {
                    return true;
                }
            }

            return false;
        }

        public override void Scale(double scaleFactor)
        {
            base.Scale(scaleFactor);
            this.Speed.Scale(scaleFactor);
        }

        #endregion

    }
}
